/*
 *  Assembler.cpp
 *  vorpal
 *
 *  Created by rwebb on 25/04/08.
 *
 */

#include "Assembler.h"

namespace Vorpal{

void PrintInstruction(ExecutionInfo &code, int indentLevel){
	loop(i, 0, indentLevel){
		printf("   ");
	}
	int opcode = code.FromIp();
	printf("%s", instructions[opcode].name);
	code.NextIp();
	
	loop(i, 0, instructions[opcode].smallOperands){
		int8_t smallOperand = code.FromIp();
		code.NextIp();
		printf(" %i", smallOperand);
	}

	switch(opcode){
		case STR: {
			int size = code.FromOp().as_uint;		
			code.NextOp();
			const char *str = (const char *)code.OperandPointer();
			printf("[%i] = '%s'\n", int(strlen(str)), str);
			code.OffsetOp(size - 1);
			break;
		}
		case CODE: {
			int size = code.FromOp().as_uint;
			printf("[%u]:\n", size);	
			code.NextOp();
			Disassemble(PrintInstruction, code.OperandPointer(), indentLevel+1);
			code.OffsetOp(size - 1);
			break;
		}
		case NUM: {
			Cell cellOperand = code.FromOp();
			code.NextOp();
			printf(" %.4f\n", double(cellOperand.as_float));
			break;
		}
		case MESSAGE: {
			Cell cellOperand = code.FromOp();
			code.NextOp();
			printf(" %x\n", cellOperand.as_uint);
			break;
		}
		case JUMP:
		case JUMP_F: {
			Cell cellOperand = code.FromOp();
			code.NextOp();
			printf(" %i, %i\n", int(cellOperand.jump.ip), int(cellOperand.jump.op));
			break;
		}
		default: {
			Assert(instructions[opcode].bigOperands != -1);
			if(instructions[opcode].bigOperands == 1){
				Cell cellOperand = code.FromOp();
				code.NextOp();
				printf(" %x", cellOperand.as_uint);
			}
			printf("\n");
			break;
		}
	}
}

void Disassemble(InstructionCallback callback, const Cell *start, int indentLevel){
	ExecutionInfo code(start);
	while(!code.AtEnd()){
		callback(code, indentLevel);
	}
}

void Disassemble(const Cell *start){
	Disassemble(PrintInstruction, start, 0);
}

Assembler& Assembler::StartMethod(uint32_t numParameters, uint32_t varParamIndex){
	open = true;
	Reset();
	NumParams(numParameters, varParamIndex);
	return *this;
}

Assembler& Assembler::Number(double value){
	Cell cell;
	if(fmod(value, 1) == 0 && -128 <= value && value <= 127){
		Add(SMALL, cell, int(value));
	}
	else{
		cell.as_float = value;
		Add(NUM, cell);
	}
	return *this;
}

Assembler& Assembler::Send(uint32_t numArgs, const char *msgStr){
	Cell cell; 
	cell.as_message = 0;
	if(msgStr){
		cell.as_message = msg(msgStr);
	}
	Add(MESSAGE, cell, numArgs);
	lastSendCount = OpcodeCount();
	return *this;
}

Assembler& Assembler::Symbol(const char *name){
	Cell cell; 
	cell.as_message = msg(name);
	Add(SYMBOL, cell);
	return *this;
}

Assembler& Assembler::Get(int index){ // args are at negative indexes from locals
	Cell cell; 
	Assert(index == int8_t(index));
	Add(GET, cell, index); 
	return *this;
}

Assembler& Assembler::Set(int index){ // args are at negative indexes from locals
	Cell cell; 
	Assert(index == int8_t(index));
	Add(SET, cell, index); 
	return *this;
}

Assembler& Assembler::ObjectGet(const char *str){
	Cell cell; 
	cell.as_message = 0;
	if(str){
		cell.as_message = msg(str);
	}
	Add(OBJ_GET, cell);
	return *this;
}

Assembler& Assembler::ObjectSet(const char *str){
	Cell cell; 
	cell.as_message = 0;
	if(str){
		cell.as_message = msg(str);
	}
	Add(OBJ_SET, cell);
	return *this;
}

Assembler& Assembler::Return(int index){
	if(index != 0 && OpcodeCount() == lastSendCount){
		// tail call will happen if a message send was the previous instruction
		// no storage of return needed
		index = kTailCallMarker;
	}
	Cell cell; 
	cell.as_uint = 0;
	Assert(index == int8_t(index));
	Add(RETURN, cell, index);
	return *this;
}

Assembler& Assembler::Drop(){
	Cell cell; 
	cell.as_uint = 0;
	Add(DROP, cell);
	return *this;
}

Assembler& Assembler::Object(uint32_t numArgs){
	Cell cell; 
	Add(OBJ, cell, numArgs);
	return *this;
}

Assembler& Assembler::String(const char *str){
	Cell cell; 
	cell.as_uint = 0;
	Add(STR, cell);
	// add the string to the code
	AddOperandData(str, strlen(str) + 1, true);	
	//printf("Assembler String: %s", str);

	return *this;
}

Assembler& Assembler::InitialiseArrayElement(const char *identifier, uint32_t element){
	//cout << "Creating array element " << element << endl;
	Cell cell;
	cell.as_message = msg(identifier);
	cell.as_uint += element;
	Add(OBJ_SET, cell);
	return *this;
}

Assembler& Assembler::SetArrayElement(const char *str, uint32_t index){
	//cout << "Storing value in array " << str << ", element " << index << endl;
	Cell cell; 
	cell.as_message = msg(str);
	cell.as_uint += index;
	Add(OBJ_SET, cell);
	return *this;
}

Assembler& Assembler::GetArrayElement(const char *str, uint32_t index){
	//cout << "Getting value from array " << str << ", element " << index << endl;
	Cell cell; 
	cell.as_message = msg(str);
	cell.as_uint += index;
	Add(OBJ_GET, cell);
	return *this;
}

Assembler& Assembler::Code(const Cell *code, uint32_t codeSize){
	Cell cell; 
	cell.as_uint = 0;
	Add(CODE, cell);
	
	// add the code data to the code
	AddOperandData((const char *)code, codeSize*sizeof(*code), true);	
	return *this;
}

Assembler& Assembler::Label(const char *str){
	CodeWriter::Label(str);
	return *this;
}

Assembler& Assembler::Jump(const char *str){
	Cell cell;
	cell.as_uint = 0;
	JumpPatchPoint(str);
	Add(JUMP, cell);	
	return *this;
}

Assembler& Assembler::JumpFalse(const char *str){
	Cell cell;
	cell.as_uint = 0;
	JumpPatchPoint(str);
	Add(JUMP_F, cell);
	return *this;
}

Assembler& Assembler::EndMethod(uint32_t numLocals){
	Assert(open);
	NumLocals(numLocals);
	PatchJumps();
	open = false;
	return *this;
}

void Assembler::Add(int8_t newCode, Cell cellOperand, int8_t smallOperand){
	Assert(open);
	Assert(newCode < NumOpcodes);
	
	WriteInstruction(newCode);
	Assert(instructions[int(newCode)].smallOperands < 2); // multiple operands not written
	Assert(instructions[int(newCode)].bigOperands < 2); // multiple operands not written
	if(instructions[int(newCode)].smallOperands){
		WriteInstruction(smallOperand);
	}
	if(instructions[int(newCode)].bigOperands == 1){ // vairable length operands are written elsewhere
		WriteOperand(cellOperand);
	}
}

}

